#ifndef MODEL_ANIMATION_H
#define MODEL_ANIMATION_H

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <cstdint>
#include <vector>
#include <iostream>
#include <exception>
#include <sstream>
#include <Cache.h>
#include <memory>
#include "File.h"
#include "Skeleton.h"
#include "Utils.h"

class SkeletonAsset;

enum AnimBlendType {
    MatrixMix,
    MatrixMixDropScale,
    SRTMix,
};

enum AnimBlendSpace {
    Global,
    Parent,
};


struct SRT {
    glm::vec3 scale;
    glm::quat rotation;
    glm::vec3 translation;

    SRT(const glm::vec3 scale, const glm::quat rotation, const glm::vec3 translation) 
    : scale(scale), rotation(rotation), translation(translation) {}

    // identity
    SRT(){
        scale = glm::vec3(1.0f);
        rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
        translation = glm::vec3(0.0f);
    }


    static inline SRT Mat4ToSRT(glm::mat4 transform)
    {
        auto s = ExtractScale(transform);
        auto q = ExtractQuaternion(ExtractRotationMatrix(transform, s));
        auto t = ExtractTranslation(transform);
        return SRT(s, q, t);
    }

    static inline glm::mat4 SRTtoMat4(SRT srt) {
        glm::mat4 m = glm::scale(glm::mat4_cast(srt.rotation), srt.scale);
        // return glm::translate(m, srt.translation);
        m[3] = glm::vec4(srt.translation, 1.0f);
        return m;
    }

    // 先应用自身SRT变换，再应用输入参数SRT变换，自身位移会影响结果的缩放与旋转
    SRT Then(SRT then)
    {
        return Mat4ToSRT(SRTtoMat4(then) * SRTtoMat4(*this));
    }

};

class AnimationAsset
{
private:
    std::vector<std::shared_ptr<std::vector<SRT>>> cache;
    // std::vector<std::shared_ptr<std::vector<glm::mat4>>> cacheMat;
    std::unordered_map<int, std::string> boneIdtoNames;
    std::string name;

public:
    explicit AnimationAsset(std::vector<std::shared_ptr<std::vector<SRT>>> frames,
                            std::unordered_map<int, std::string> boneIdtoNames,
                            std::string animName) : cache(std::move(frames)), boneIdtoNames(boneIdtoNames){
                                name = animName.c_str();
                                // std::cout<<name<<std::endl;
                            }



    // [[nodiscard]] inline std::shared_ptr<std::vector<glm::mat4>> GetFrameMat4(uint32_t frameId)
    // {
    //     if (frameId < cache.size())
    //         return cacheMat[frameId];
    //     std::stringstream ss;
    //     ss << "error find frame [" << frameId << "]" << std::endl;
    //     throw std::out_of_range(ss.str());
    // }

    void OrderAnimation(std::shared_ptr<SkeletonAsset> skeletonAsset);
};

struct OrderedAnimation{
public:
    std::string name;
    friend class AnimationAsset;

    OrderedAnimation(std::vector<std::shared_ptr<std::vector<SRT>>> frames, std::unordered_map<int, std::string> idtoNames)
    {
        //深拷贝
        cache = frames;
        boneIdtoNames = idtoNames;
    }

    [[nodiscard]] inline std::shared_ptr<std::vector<SRT>> GetFrameAnim(uint32_t frameId)
    {
        if (frameId < cache.size())
            return cache[frameId];
        std::stringstream ss;
        ss << "error find frame [" << frameId << "]" << std::endl;
        throw std::out_of_range(ss.str());
    }

    [[nodiscard]] inline uint32_t GetFrameSize() const
    {
        if (cache.size() == 0)
            return 1;
        return cache.size();
    }

    void Export(std::string fileName);
    OrderedAnimation(std::string fileName, std::shared_ptr<SkeletonAsset> skeleton);

private:
    std::vector<std::shared_ptr<std::vector<SRT>>> cache;
    std::unordered_map<int, std::string> boneIdtoNames;
};

#endif //MODEL_ANIMATION_H
